﻿using System;
using System.IO;

namespace MSPToolkit.Compression
{
    internal class DeflaterOutputStream : Stream
    {
        protected byte[] buf;

        protected Deflater def;

        protected Stream baseOutputStream;

        public bool CanPatchEntries
        {
            get
            {
                return baseOutputStream.CanSeek;
            }
        }

        public override bool CanRead
        {
            get
            {
                return baseOutputStream.CanRead;
            }
        }

        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return baseOutputStream.CanWrite;
            }
        }

        public override long Length
        {
            get
            {
                return baseOutputStream.Length;
            }
        }

        public override long Position
        {
            get
            {
                return baseOutputStream.Position;
            }
            set
            {
                throw new NotSupportedException("DefalterOutputStream Position not supported");
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException("DeflaterOutputStream Seek not supported");
        }

        public override void SetLength(long val)
        {
            throw new NotSupportedException("DeflaterOutputStream SetLength not supported");
        }

        public override int ReadByte()
        {
            throw new NotSupportedException("DeflaterOutputStream ReadByte not supported");
        }

        public override int Read(byte[] b, int off, int len)
        {
            throw new NotSupportedException("DeflaterOutputStream Read not supported");
        }

        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            throw new NotSupportedException("DeflaterOutputStream BeginRead not currently supported");
        }

        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            throw new NotSupportedException("DeflaterOutputStream BeginWrite not currently supported");
        }

        protected void Deflate()
        {
            while (!def.IsNeedingInput)
            {
                int len = def.Deflate(buf, 0, buf.Length);

                if (len <= 0)
                {
                    break;
                }

                if (this.Password != null)
                {
                    this.EncryptBlock(buf, 0, len);
                }

                baseOutputStream.Write(buf, 0, len);
            }
        }

        public DeflaterOutputStream(Stream baseOutputStream)
            : this(baseOutputStream, new Deflater(), 512)
        {
        }

        public DeflaterOutputStream(Stream baseOutputStream, Deflater defl)
            : this(baseOutputStream, defl, 512)
        {
        }

        public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufsize)
        {
            if (baseOutputStream.CanWrite == false)
            {
                throw new ArgumentException("baseOutputStream", "must support writing");
            }

            if (deflater == null)
            {
                throw new ArgumentNullException("deflater");
            }

            if (bufsize <= 0)
            {
                throw new ArgumentOutOfRangeException("bufsize");
            }

            this.baseOutputStream = baseOutputStream;
            buf = new byte[bufsize];
            def = deflater;
        }

        public override void Flush()
        {
            def.Flush();
            Deflate();
            baseOutputStream.Flush();
        }

        public virtual void Finish()
        {
            def.Finish();
            while (!def.IsFinished)
            {
                int len = def.Deflate(buf, 0, buf.Length);
                if (len <= 0)
                {
                    break;
                }

                if (this.Password != null)
                {
                    this.EncryptBlock(buf, 0, len);
                }

                baseOutputStream.Write(buf, 0, len);
            }
            baseOutputStream.Flush();
        }

        public override void Close()
        {
            Finish();
            baseOutputStream.Close();
        }

        public override void WriteByte(byte bval)
        {
            byte[] b = new byte[1];
            b[0] = bval;
            Write(b, 0, 1);
        }

        public override void Write(byte[] buf, int off, int len)
        {
            def.SetInput(buf, off, len);
            Deflate();
        }

        #region Encryption

        string password = null;
        uint[] keys = null;

        /// </summary>
        public string Password
        {
            get
            {
                return password;
            }
            set
            {
                password = value;
            }
        }


        protected byte EncryptByte()
        {
            uint temp = ((keys[2] & 0xFFFF) | 2);
            return (byte)((temp * (temp ^ 1)) >> 8);
        }


        protected void EncryptBlock(byte[] buffer, int offset, int length)
        {
            for (int i = offset; i < offset + length; ++i)
            {
                byte oldbyte = buffer[i];
                buffer[i] ^= EncryptByte();
                UpdateKeys(oldbyte);
            }
        }

        protected void InitializePassword(string password)
        {
            keys = new uint[] {
				0x12345678,
				0x23456789,
				0x34567890
			};

            for (int i = 0; i < password.Length; ++i)
            {
                UpdateKeys((byte)password[i]);
            }
        }

        protected void UpdateKeys(byte ch)
        {
            keys[0] = Crc32.ComputeCrc32(keys[0], ch);
            keys[1] = keys[1] + (byte)keys[0];
            keys[1] = keys[1] * 134775813 + 1;
            keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24));
        }
        #endregion
    }
}
